/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce::midi_ci
{

/**
    Accumulates message chunks that have been sent by another device in response
    to a transaction initiated by a local device.

    @tags{Audio}
*/
class InitiatorPropertyExchangeCache
{
public:
    InitiatorPropertyExchangeCache();
    ~InitiatorPropertyExchangeCache();

    InitiatorPropertyExchangeCache (InitiatorPropertyExchangeCache&&) noexcept;
    InitiatorPropertyExchangeCache& operator= (InitiatorPropertyExchangeCache&&) noexcept;

    JUCE_DECLARE_NON_COPYABLE (InitiatorPropertyExchangeCache)

    /** Holds a token that can be used to stop waiting for a reply, along with
        an identifier byte that uniquely identifies an ongoing transaction.

        @tags{Audio}
    */
    struct TokenAndId
    {
        TokenAndId() = default;
        TokenAndId (ErasedScopeGuard tokenIn, std::byte idIn)
            : token (std::move (tokenIn)), id (idIn) {}

        bool isValid() const { return (id & std::byte { 0x80 }) == std::byte{}; }

        ErasedScopeGuard token{};
        std::byte id { 0x80 };
    };

    /** Picks an unused request ID, and prepares the cache for that ID to accumulate message chunks.

        Incoming chunks added with addChunk are generated by another device acting as a responder.
    */
    TokenAndId primeCache (uint8_t maxSimultaneousRequests,
                           std::function<void (const PropertyExchangeResult&)> onDone,
                           std::function<void (std::byte)> onTerminate);

    /** Adds a message chunk for the provided transaction id. */
    void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk);

    /** Updates the transaction state based on the contents of the provided notification. */
    void notify (std::byte b, Span<const std::byte> header);

    /** Returns the number of transactions that have been started but not finished. */
    int countOngoingTransactions() const;

    /** Returns true if there are any transactions in progress that
        haven't yet received replies.
    */
    bool isAwaitingResponse() const;

private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

//==============================================================================
/**
    Accumulates message chunks that form a request initiated by a remote device.

    @tags{Audio}
*/
class ResponderPropertyExchangeCache
{
public:
    ResponderPropertyExchangeCache();
    ~ResponderPropertyExchangeCache();

    ResponderPropertyExchangeCache (ResponderPropertyExchangeCache&&) noexcept;
    ResponderPropertyExchangeCache& operator= (ResponderPropertyExchangeCache&&) noexcept;

    JUCE_DECLARE_NON_COPYABLE (ResponderPropertyExchangeCache)

    /** Prepares the cache for the given requestId to accumulate message chunks.

        Incoming chunks added with addChunk are generated by another device acting as an initiator.
    */
    void primeCache (uint8_t maxSimultaneousTransactions,
                     std::function<void (const PropertyExchangeResult&)> onDone,
                     std::byte id);

    /** Adds a message chunk for the provided transaction id. */
    void addChunk (std::byte b, const Message::DynamicSizePropertyExchange& chunk);

    /** Updates the transaction state based on the contents of the provided notification. */
    void notify (std::byte b, Span<const std::byte> header);

    /** Returns the number of transactions that have been started but not finished. */
    int countOngoingTransactions() const;

private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

//==============================================================================
/**
    An interface for objects that provide resources for property exchange
    transactions.

    @tags{Audio}
*/
class CacheProvider
{
public:
    virtual ~CacheProvider() = default;

    /** Returns a set containing all of the MUIDs currently known to the provider. */
    virtual std::set<MUID> getDiscoveredMuids() const = 0;

    /** Returns a property exchange cache for accumulating replies to transactions
        we initiated.
    */
    virtual InitiatorPropertyExchangeCache* getCacheForMuidAsInitiator (MUID m) = 0;

    /** Returns a property exchange cache for accumulating requests initiated
        by other devices.
    */
    virtual ResponderPropertyExchangeCache* getCacheForMuidAsResponder (MUID m) = 0;

    /** Returns the maximum sysex size supported by the device with the
        given MUID.
    */
    virtual int getMaxSysexSizeForMuid (MUID m) const = 0;
};

} // namespace juce::midi_ci
